package com.webchat.pgc.service.mall;


import com.fasterxml.jackson.core.type.TypeReference;
import com.webchat.common.config.annotation.ExecAfterCall;
import com.webchat.common.enums.CommonStatusEnum;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.service.RedisService;
import com.webchat.common.util.JsonUtil;
import com.webchat.domain.vo.request.mall.SaveMallCategoryRequestVO;
import com.webchat.domain.vo.response.mall.MallCategoryBaseLevelVO;
import com.webchat.domain.vo.response.mall.MallCategoryResponseVO;
import com.webchat.pgc.repository.dao.mall.IMallCategoryDAO;
import com.webchat.pgc.repository.entity.mall.MallCategory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class MallCategoryService {

    @Autowired
    private IMallCategoryDAO mallCategoryDAO;

    @Autowired
    private RedisService redisService;

    @Autowired
    private RedissonClient redissonClient;

    /**
     *
     * 创建、更新商品分类
     *
     * @param requestVO
     * @return
     */
    @ExecAfterCall(clazz = MallCategoryService.class, method = "refreshCache")
    public Long saveCategory(SaveMallCategoryRequestVO requestVO) {

        MallCategory category = this.convert(requestVO);
        return mallCategoryDAO.save(category).getId();
    }

    /**
     * 发布分类
     *
     * @param id
     * @param userId
     * @return
     */
    @ExecAfterCall(clazz = MallCategoryService.class, method = "refreshCache")
    public boolean publish(Long id, String userId) {

        return this.updateStatus(id, userId, CommonStatusEnum.PUBLISHED);
    }

    /**
     * 撤回分类
     *
     * @param id
     * @param userId
     * @return
     */
    @ExecAfterCall(clazz = MallCategoryService.class, method = "refreshCache")
    public boolean invoke(Long id, String userId) {

        return this.updateStatus(id, userId, CommonStatusEnum.BACK);
    }

    /**
     * 删除分类
     *
     * @param id
     * @param userId
     * @return
     */
    @ExecAfterCall(clazz = MallCategoryService.class, method = "refreshCache")
    public boolean delete(Long id, String userId) {

        return this.updateStatus(id, userId, CommonStatusEnum.DELETED);
    }


    public boolean updateStatus(Long id, String userId, CommonStatusEnum statusEnum) {

        MallCategory category = mallCategoryDAO.findById(id).orElse(null);
        Assert.notNull(category, "状态更新失败，分类不存在");
        Integer oldStatus = category.getStatus();
        Integer newStatus = statusEnum.getStatusCode();
        category.setStatus(newStatus);
        category.setUpdateBy(userId);
        mallCategoryDAO.save(category);
        log.info("商品分类状态更新 ===> 更新分类{}, userId:{}, status:{}-{}",
                category.getName(), userId, oldStatus, newStatus);
        return true;
    }

    /**
     * 修改分类优先级
     *
     * @param id
     * @param rank
     * @param userId
     * @return
     */
    @ExecAfterCall(clazz = MallCategoryService.class, method = "refreshCache")
    public boolean updateRank(Long id, Integer rank, String userId) {

        MallCategory category = mallCategoryDAO.findById(id).orElse(null);
        Assert.notNull(category, "状态更新失败，分类不存在");
        category.setSort(rank);
        category.setUpdateBy(userId);
        mallCategoryDAO.save(category);
        log.info("商品分类优先级更新 ===> 更新分类{}, userId:{}, rank:{}",
                category.getName(), userId, rank);
        return true;
    }


    public MallCategoryBaseLevelVO get(Long categoryId) {

        // 1. 走redis获取圈量产品分类树
        List<MallCategoryResponseVO> list = this.listAll();
        // 2. 递归获取商品分类
        MallCategoryResponseVO isCurrentCategory = deepCompareCategory(list, categoryId);
        if (isCurrentCategory != null) {
            return null;
        }
        return null;
    }

    public List<MallCategoryResponseVO> buildReversePath(MallCategoryResponseVO isCurrentCategory,
                                                         List<MallCategoryResponseVO> array) {
        if (isCurrentCategory == null) {
            return array;
        }
        array.add(isCurrentCategory);
        buildReversePath(isCurrentCategory.getParent(), array);
        return array;
    }

    private MallCategoryResponseVO deepCompareCategory(List<MallCategoryResponseVO> list, Long categoryId) {

        for (MallCategoryResponseVO root : list) {
            if (root.getId().equals(categoryId)) {
                return root;
            }
            deepCompareCategory(root.getChildren(), categoryId);
        }
        return null;
    }

    /**
     * 分类查询，走redis
     *
     * @return
     */
    public List<MallCategoryResponseVO> listAll() {

        String key = RedisKeyEnum.MALL_CATEGORY_LIST_CACHE.getKey();
        String cache = redisService.get(key);
        if (StringUtils.isBlank(cache)) {
            String lockKey = RedisKeyEnum.MALL_CATEGORY_LIST_CACHE_REFRESH_LOCK.getKey();
            RLock rLock = redissonClient.getLock(lockKey);
            rLock.lock();
            try {
                cache = redisService.get(key);
                if (StringUtils.isBlank(cache)) {
                    return this.refreshCache();
                }
            } finally {
                rLock.unlock();
            }
        }
        // 缓存数据反序列化
        return JsonUtil.fromJson(cache, new TypeReference<List<MallCategoryResponseVO>>() {});
    }


    /**
     * 刷新商品分类缓存
     *
     * @return
     */
    public List<MallCategoryResponseVO> refreshCache() {
        List<MallCategoryResponseVO> categories = new ArrayList<>();
        List<MallCategory> allCategories =
                mallCategoryDAO.findAllByStatusOrderBySortDescIdDesc(CommonStatusEnum.PUBLISHED.getStatusCode());
        if (CollectionUtils.isNotEmpty(allCategories)) {
            // 构造客户端所需要商品分类树
            categories = this.buildCategoryTree(allCategories);
            // 树各层级进行重排序
            this.sort(categories);
        }
        // 刷新分类redis缓存
        this.setCache(categories);
        return categories;
    }

    /**
     * 刷新商品分类缓存
     *
     * @param categories
     */
    private void setCache(List<MallCategoryResponseVO> categories) {
        categories = categories == null ? Collections.emptyList() : categories;
        String key = RedisKeyEnum.MALL_CATEGORY_LIST_CACHE.getKey();
        redisService.set(key, JsonUtil.toJsonString(categories), RedisKeyEnum.MALL_CATEGORY_LIST_CACHE.getExpireTime());
    }

    /**
     * 商品分类转树结构
     *
     * @param allCategories
     * @return
     */
    private List<MallCategoryResponseVO> buildCategoryTree(List<MallCategory> allCategories) {

        if (CollectionUtils.isEmpty(allCategories)) {
            return Collections.emptyList();
        }
        List<MallCategoryResponseVO> categories = new ArrayList<>();
        List<MallCategoryResponseVO> nodes = allCategories.stream().map(this::convert).collect(Collectors.toList());
        Map<Long, MallCategoryResponseVO> nodeMap = new HashMap<>();
        for (MallCategoryResponseVO node : nodes) {
            nodeMap.put(node.getId(), node);
        }
        // 构造树
        for (MallCategoryResponseVO node : nodes) {
            Long parentId = node.getParentId();
            if (parentId == null) {
                // 说明根节点
                categories.add(node);
            } else {
                MallCategoryResponseVO parent = nodeMap.get(parentId);
                if (parent != null) {
                    // 设置子节点
                    parent.getChildren().add(node);
                    // 设置上级父节点
                    node.setParent(parent);
                }
            }
        }
        return categories;
    }

    /**
     * 分类树排序
     *
     * @param categories
     */
    private void sort(List<MallCategoryResponseVO> categories) {

        if (CollectionUtils.isEmpty(categories)) {
            return;
        }
        // 排序根节点
        categories.sort(Comparator.comparing(MallCategoryResponseVO::getSort, Comparator.reverseOrder())
                  .thenComparing(MallCategoryResponseVO::getId, Comparator.reverseOrder()));
        // 子节点排序
        for (MallCategoryResponseVO category : categories) {
            // 递归排序树的子节点数据
            if (CollectionUtils.isNotEmpty(category.getChildren())) {
                this.sort(category.getChildren());
            }
        }
    }

    /**
     * VO -> Entity
     *
     * @param requestVO
     * @return
     */
    private MallCategory convert(SaveMallCategoryRequestVO requestVO) {
        Long id = requestVO.getId();
        MallCategory mallCategory;
        Date now = new Date();
        if (id != null) {
            mallCategory = mallCategoryDAO.findById(id).orElse(null);
            Assert.notNull(mallCategory, "保存失败:未查找到分类");

        } else {
            mallCategory = new MallCategory();
            mallCategory.setSort(0);
            mallCategory.setStatus(CommonStatusEnum.NEW.getStatusCode());
            mallCategory.setCreateBy(requestVO.getOperator());
            mallCategory.setCreateDate(now);
        }
        mallCategory.setName(requestVO.getName());
        mallCategory.setIcon(requestVO.getIcon());
        mallCategory.setSort(requestVO.getRank());
        mallCategory.setParentId(requestVO.getParentId());
        mallCategory.setUpdateBy(requestVO.getOperator());
        mallCategory.setUpdateDate(now);
        return mallCategory;
    }

    /**
     * entity -> VO
     *
     * @param category
     * @return
     */
    private MallCategoryResponseVO convert(MallCategory category) {
        if (category == null) {
            return null;
        }
        MallCategoryResponseVO vo = new MallCategoryResponseVO();
        BeanUtils.copyProperties(category, vo);
        return vo;
    }
}
